use arc_swap::ArcSwapOption;
use clickhouse_rs::Pool;
use once_cell::sync::{Lazy, OnceCell};
use redis::Client;
use serde::{Deserialize, Serialize};
use sqlx::{mysql::MySqlPoolOptions, MySql, MySqlPool};
use std::{fs, sync::Arc};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct Config {
    pub app_name: String,
    pub mysql: Database,
    pub clickhouse: Database,
    pub redis: Redis,
    pub jwt: JwtConfig,
    pub mail: Mail,
    pub site_addr: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct Database {
    pub host: String,
    pub port: u32,
    pub user: String,
    pub password: String,
    pub database: String,
    pub other_params: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct Redis {
    pub server: String,
    pub port: u32,
    pub password: String,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct JwtConfig {
    pub issuer: String,
    pub secret: String,
    pub expire: u8,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct Mail {
    pub user: String,
    pub pass: String,
    pub host: String,
    pub port: u32,
}
static WORK_PATH: OnceCell<String> = OnceCell::new();
pub fn get_work_path() -> &'static str {
    let wp =
        WORK_PATH.get_or_init(|| std::env::var("SHQ_PAS_CONF_PATH").unwrap_or("./".to_string()));
    wp
}
static CONF: Lazy<ArcSwapOption<&'static Config>> = Lazy::new(|| ArcSwapOption::const_empty());
static CH_POOL: Lazy<ArcSwapOption<&'static Pool>> = Lazy::new(|| ArcSwapOption::empty());
static REDIS_CLI: Lazy<ArcSwapOption<&'static Client>> = Lazy::new(|| ArcSwapOption::empty());
static MY_POOL: Lazy<ArcSwapOption<&'static MySqlPool>> =
    Lazy::new(|| ArcSwapOption::empty());

pub async fn init_app_config() -> anyhow::Result<()> {
    let file_path = format!("{}/config_files/app.yaml", get_work_path());
    let conf_str = fs::read_to_string(file_path)?;
    let conf: Config = serde_yaml::from_str(&conf_str)
        .or_else(|err| Err(anyhow::anyhow!("解析配置文件app.yaml出错，错误信息{}", err)))?;
    //连接mysql
    let my_conn = format!(
        "mysql://{}:{}@{}:{}/{}?{}",
        conf.mysql.user,
        conf.mysql.password,
        conf.mysql.host,
        conf.mysql.port,
        conf.mysql.database,
        conf.mysql.other_params
    );
    let my_pool = MySqlPoolOptions::new()
        .max_connections(100)
        .min_connections(5)
        .idle_timeout(std::time::Duration::from_secs(60))
        .max_lifetime(std::time::Duration::from_secs(60))
        .connect(&my_conn).await.or_else(|err|Err(anyhow::anyhow!("连接mysql失败：{}",err)))?;
    let my_pool_ref = Box::leak(Box::new(my_pool));
    MY_POOL.store(Some(Arc::new(my_pool_ref)));
    //连接clickhouse
    let ch_conn = format!(
        "tcp://{}:{}@{}:{}/{}?{}",
        conf.clickhouse.user,
        conf.clickhouse.password,
        conf.clickhouse.host,
        conf.clickhouse.port,
        conf.clickhouse.database,
        conf.clickhouse.other_params
    );
    let ch_pool = Pool::new(ch_conn);
    let ch_pool_ref = Box::leak(Box::new(ch_pool));
    CH_POOL.store(Some(Arc::new(ch_pool_ref)));
    //连接redis
    let ers_conn_str = format!(
        "redis://default:{}@{}:{}/",
        conf.redis.password, conf.redis.server, conf.redis.port
    );
    let redis_cli = redis::Client::open(ers_conn_str.as_str())
        .or_else(|err| Err(anyhow::anyhow!("redis连接失败:{}", err)))?;
    let redis_cli_ref = Box::leak(Box::new(redis_cli));
    REDIS_CLI.store(Some(Arc::new(redis_cli_ref)));
    let conf_ref = Box::leak(Box::new(conf));
    CONF.store(Some(Arc::new(conf_ref)));
    Ok(())
}

pub fn get_app_config<'a>() -> &'static Config {
    CONF.load().clone().unwrap().as_ref()
}
pub fn get_my_pool() -> &'static sqlx::pool::Pool<MySql> {
    MY_POOL.load().clone().unwrap().as_ref()
}
pub fn get_ch_pool() -> &'static Pool {
    CH_POOL.load().clone().unwrap().as_ref()
}
pub fn get_redis_cli() -> &'static redis::Client {
    REDIS_CLI.load().clone().unwrap().as_ref()
}
